package com.easyaop.asm;

import com.easyaop.api.LoggerFactory;
import com.easyaop.api.advisor.Advisor;
import com.easyaop.api.pointcut.ClassObject;
import com.easyaop.api.pointcut.MethodObject;
import com.easyaop.api.pointcut.Pointcut;
import com.easyaop.core.Inst;
import com.easyaop.core.event.Event;
import com.easyaop.core.event.EventListener;
import com.easyaop.core.event.EventSource;
import com.easyaop.core.event.PointcutEvent;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Driver implements EventListener {
    private static final Logger LOGGER = LoggerFactory.getLogger("PointcutEvent");

    @Override
    public void onEvent(Event event) {
        PointcutEvent aopEvent = (PointcutEvent) event;

        weaving(aopEvent.getChanges());
    }

    private void weaving(Set<Advisor> advisors) {
        Class[] allLoadedClasses = Inst.inst.getAllLoadedClasses();
        Set<Class> classes = new HashSet<>();
        Arrays.stream(allLoadedClasses)
                .forEach(clz -> {
                    for (Advisor advisor : advisors) {
                        if (matchPointcut(advisor.getPointcut(), clz)) {
                            classes.add(clz);
                            break;
                        }
                    }
                });
        try {
            if (classes.isEmpty()) {
                return;
            }
            LOGGER.info("weaving " + classes);
            Inst.inst.retransformClasses(classes.toArray(new Class[0]));
        } catch (Throwable e) {
            LOGGER.log(Level.WARNING, "" + e, e);
        }
    }

    private boolean matchPointcut(Pointcut pointcut, Class clz) {
        try {
            if (!pointcut.isClass(new ClassObject.ForClass(clz))) {
                return false;
            }
            Method[] declaredMethods = clz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (pointcut.isMethod(new MethodObject.ForMethod(declaredMethod))) {
                    return true;
                }
            }
            return false;
        } catch (Throwable e) {
            LOGGER.log(Level.WARNING, "Class: " + clz + ", msg22: " + e.getClass().getName() + " - " + e.getMessage(),
                    e);
            return false;
        }
    }

    static {
        Inst.inst.addTransformer(new AsmTransformer(), true);
        EventSource.EVENT_SOURCE.addEventListener(new Driver());
    }
}
